﻿using System;

using OpenTK;
using OpenTK.Graphics.OpenGL;

namespace GLSL
{
    /// <summary>
    /// Handles the creation and management of GLSL Shaders,
    /// Requires a Vertex Shader and Fragment Shader to Function
    /// </summary>
    public class GLSLShader
    {
        public struct Shader
        {
            public int id;
            public ShaderType Type;
        }

        private int ShaderProgramID;
        public Shader VertexShader;
        public Shader FragShader;
        private int TexturePostionUniform;

        public GLSLShader()
        {
            this.ShaderProgramID = GL.CreateProgram();
        }

        /// <summary>
        /// Creates and Compiles a new Shader
        /// </summary>
        /// <param name="shaderType">Type of Shader Vertex or Fragment</param>
        /// <param name="fileName">The String containg the source for the shader</param>
        /// <returns>A Struct containing the ID and the type</returns>
        static public Shader Create(ShaderType shaderType, string shaderSource)
        {
            int shader = GL.CreateShader(shaderType);
            GL.ShaderSource(shader, shaderSource);
            GL.CompileShader(shader);

            int status;
            GL.GetShader(shader, ShaderParameter.CompileStatus, out status);

            if (status == 0)
            {
                var errorLog = GL.GetShaderInfoLog(shader);
                throw new Exception(String.Format("Compile failure in {0}: {1}\n", shaderSource, errorLog));
            }

            return new Shader { id = shader, Type = shaderType };
        }

        /// <summary>
        /// Complies the shader program
        /// </summary>
        public GLSLShader Complie()
        {
            GL.AttachShader(this.ShaderProgramID, this.FragShader.id);
            GL.AttachShader(this.ShaderProgramID, this.VertexShader.id);
            GL.LinkProgram(this.ShaderProgramID);
            GL.UseProgram(this.ShaderProgramID);

            TexturePostionUniform = GL.GetUniformLocation(this.ShaderProgramID, "Texture0");
            GL.Uniform1(TexturePostionUniform, 0);

            int result;
            GL.GetProgram(this.ShaderProgramID, ProgramParameter.LinkStatus, out result);

            if (result == 0)
            {
                Console.WriteLine("Failed to link shader program!");
                Console.WriteLine(GL.GetProgramInfoLog(this.ShaderProgramID));
            }

            //Reset what shader OpenGL should use
            GL.UseProgram(0);

            return this;
        }

        /// <summary>
        /// Set the OpenGL to use the current shader.
        /// </summary>
        public void BindShader()
        {
            GL.UseProgram(ShaderProgramID);
        }

        /// <summary>
        /// resets the binder
        /// </summary>
        public static void Unbind()
        {
            GL.UseProgram(0);
        }

        public void Uniform1(string name, float f)
        {
            int loc = GL.GetUniformLocation(this.ShaderProgramID, name);
            GL.Uniform1(loc, f);
        }

        public void Uniform2(string name, ref Vector2 v)
        {
            int loc = GL.GetUniformLocation(this.ShaderProgramID, name);
            GL.Uniform2(loc, v);
        }

        public void Uniform3(string name, ref Vector3 v)
        {
            int loc = GL.GetUniformLocation(this.ShaderProgramID, name);
            GL.Uniform3(loc, v);
        }

        public void Uniform4(string name, ref Vector4 v)
        {
            int loc = GL.GetUniformLocation(this.ShaderProgramID, name);
            GL.Uniform4(loc, v);
        }

        /// <summary>
        /// Set the Shader to use a specific texture fils
        /// </summary>
        /// <param name="texture">Texture</param>
        public void UseTexture(Texture texture)
        {
            GL.ActiveTexture(TextureUnit.Texture0);
            texture.bind();
            GL.Uniform1(TexturePostionUniform, 0);
        }

        /// <summary>
        /// Unset and disposes the class
        /// </summary>
        public void dispose()
        {
            GL.DeleteShader(VertexShader.id);
            VertexShader = default(Shader);

            GL.DeleteProgram(FragShader.id);
            FragShader = default(Shader);

            GL.DeleteProgram(ShaderProgramID);
            ShaderProgramID = 0;
        }
    }
}
